
Norm.rowIdCounter = 0;

Norm.Model = function(options) {
	
	if (! options || ! options.fields || ! options.name) {
		throw new Error("model must have fields and name");
	}
	
	var model = function(valuesOrRowID) {
		
		this.modelClass = model;
		
		if (valuesOrRowID) {
			if (typeof(valuesOrRowID) == "number") {
				this.rowid = valuesOrRowID;
				this.refresh();
				return;
			} else {
				Norm.Database.extend(this, valuesOrRowID);
			}
		}
		
		for (var fieldName in options.fields) {
			var field = options.fields[fieldName];
			if (field.isBackwardRelation) {
				var copiedField = this[fieldName] = field.copy();
				copiedField.instance = this;
				copiedField.modelClass = model;
			}
		}
	};
	
	if (Norm.useIdTable) {
		options.fields["rowid"] = new Norm.Fields.Integer();										// Jaxer does not support lastInsertRowId
	}
	model.options = options;
	model.prototype = {
		
		remove : function() {
			
			if (this.rowid) {
				Norm.Database.executeAndClose(Norm.Sql.deleteRowByIdAndTable(options.name), [this.rowid]);
			}
			
			this.rowid = null;
			return this;
		},
		
		save : function() {
			return this._updateInsert(!! this.rowid);
		},
		
		_updateInsert : function(update) {
			
			if (update) {
				query = Norm.Sql.updateByModel(model);
			} else {
				if (Norm.useIdTable) {
					this.rowid = Norm.LastIdManager.getLastId(options.name);						// Jaxer does not support lastInsertRowId
					this.rowid++;																// Jaxer does not support lastInsertRowId
					Norm.LastIdManager.putLastId(options.name, this.rowid);						// Jaxer does not support lastInsertRowId
					Norm.log("lastId: " + this.rowid);												// Jaxer does not support lastInsertRowId
				}
				query = Norm.Sql.insertRowByModel(model);
			}
			var values = [];
			var fields = model.options.fields;
			for (var fieldName in fields) {
				var field = fields[fieldName];
				if (! field.isBackwardRelation) {
					var value = this[fieldName];
					if (! field.isRelation) {
						values.push(value);
					} else {
						if (typeof(value) == "number" || (! value && field.options.allowNull)) {
							values.push(value);
						} else if (value.rowid) {
							values.push(value.rowid);
						} else {
							throw new Error("value of related model can be a model instance,or a id(integer)");
						}
					}	
				}
			}
			if (update) {
				values.push(this.rowid);
			}
			Norm.Database.executeAndClose(query, values);
			if (! update) {
				if (Norm.useIdTable) {
					;																		// Jaxer does not support lastInsertRowId
				} else {
					this.rowid = Norm.Database.getLastInsertRowId();
				}
			}
			return this;
		},
		
		refresh : function() {
			
			if (this.rowid) {
				var resultIterator = model.select(options.name + ".rowid = ?", [this.rowid]);
				if (! resultIterator.isValidRow()) {
					throw new Error("instance doesn`t exist in database");
				}
				this._populateFromResult(resultIterator);
				resultIterator.close();
			}
			return this;
		},
		
		_populateFromResult : function(resultIterator) {
			
			var fields = options.fields;
			var name = options.name;
			this.rowid = resultIterator.fieldByName("rowid");					// every table in google gears have a rowid
			for (var fieldName in fields) {
				var field = fields[fieldName];
				if (! field.isBackwardRelation) {
					if (field.isRelation) {
						var relatedClass = field.getRelatedClass();
						if (relatedClass == model) {
							this[fieldName] = resultIterator.fieldByName(name + "_id");
						} else {
							this[fieldName] = (new relatedClass())._populateFromResult(resultIterator);
						}
					} else {
						this[fieldName] = resultIterator.fieldByName(name + "_" + fieldName);
					}
				} else {
					var copiedField = this[fieldName] = field.copy();
					copiedField.instance = this;
					copiedField.modelClass = model;
				}
			}
			return this;
		}
	};
	
	for (var fieldName in options.fields) {										// add reference to the model to the fields
		options.fields[fieldName].modelClass = model;
	}
	
	Norm.Database.extend(model, this);
	Norm.Database.extend(model, new Norm.Events);
	for (var i in model.prototype) {											// add events to save,remove,refresh
		if (i.charAt(0) != "_") {												// wrap only public functions
			model.prototype[i] = Norm.Events.wrapFunction(model.prototype[i], i.charAt(0).toUpperCase() + i.substring(1), model);
		}
	}
	model.constructor = Norm.Model;
	Norm._models[options.name] = model;									// register the model
	return model;
};

Norm.Model.prototype = {
	
	count : function(whereExpression, params) {
		
		var resultSet = Norm.Database.execute(Norm.Sql.selectCount(this.options.name, whereExpression), params);
		var resultIterator = new Norm.ResultIterator(resultSet, this);
		var count = resultIterator.fieldByName("c");
		resultIterator.close();
		return count;
	},
	
	select : function(whereExpression, params) {
		return new Norm.ResultIterator(Norm.Database.execute(Norm.Sql.selectWithForeignKeys(this, whereExpression), params), this);
	},
	
	remove : function(whereExpression,params) {
		Norm.Database.executeAndClose(Norm.Sql.deleteFromTableWhere(this.options.name, whereExpression), params);
	},
	
	createTable : function() {
		
		Norm.Transaction(function() {
			
			Norm.Database.executeAndClose(Norm.Sql.createTableByModelClass(this));
			var fields = this.options.fields;
			for (var fieldName in fields) {
				var field = fields[fieldName];
				if (field.createTable) {
					field.createTable();
				}
			}
			this.createTriggers();
		}, this);
	},
	
	dropTable : function() {
		
		Norm.Transaction(function() {
			Norm.Database.executeAndClose(Norm.Sql.dropTableByName(this.options.name));
			this.dropTriggers();
		}, this);
	},
	
	createTriggers : function() {
		
		if (this.options.name.indexOf("m2m") == 0) {								// in case of triggers in m2m tables check first that tables exist
			var tableNames = [];												// find out which tables are related to this table
			for (var i in this.options.fields) {
				tableNames.push(this.options.fields[i].options.related);
			}
			if (! Norm.Introspection.doesTableExist.apply(this, tableNames)) {			// if one of the tables doesn`t exist don`t create the triggers
				return;
			}
		};
		
		var fields = this.options.fields;
		var name = this.options.name;
		
		Norm.Transaction(function() {
			for (var fieldName in fields) {
				var field = fields[fieldName];
				if (field.isRelation && ! field.isBackwardRelation) {
					Norm.Database.executeAndClose(Norm.Sql.createInsertForeignKeyTrigger(name, field.getRelatedClass().options.name, field.options.allowNull));		// on insert,check that foreign key is valid since sqlite doesn`t enforce foreignkeys
					Norm.Database.executeAndClose(Norm.Sql.createUpdateForeignKeyTrigger(name, field.getRelatedClass().options.name, field.options.allowNull));		// on update,check that foreign key is valid since sqlite doesn`t enforce foreignkeys
					if (field.options.onDeleteCascade) {
						Norm.Database.executeAndClose(Norm.Sql.createDeleteCascadeTrigger(name, field.getRelatedClass().options.name));							// on delete from parent table,delete all rows that reference the row that is going to be deleted
					} else {
						Norm.Database.executeAndClose(Norm.Sql.createDeleteRestrictTrigger(name, field.getRelatedClass().options.name));							// on delete from parent table,if there is a row that reference the row that is going to be deleted throw a exception
					}
				}
			}
		});
	},
	
	dropTriggers : function() {
		
		var fields = this.options.fields;
		var name = this.options.name;
		
		Norm.Transaction(function() {
			for (var fieldName in fields) {
				var field = fields[fieldName];
				if (field.isRelation && ! field.isBackwardRelation) {
					Norm.Database.executeAndClose(Norm.Sql.dropForeignKeyInsertTrigger(name, field.getRelatedClass().options.name));
					Norm.Database.executeAndClose(Norm.Sql.dropForeignKeyUpdateTrigger(name, field.getRelatedClass().options.name));
					Norm.Database.executeAndClose(Norm.Sql.dropForeignKeyDeleteTrigger(name, field.getRelatedClass().options.name));
				} else if(field._m2mModel) {
					field._m2mModel.dropTriggers();
				}
			}
		});
	},
	
	load : function(objects, save) {
		
		var mappedObjects = []
		var modelClass = this;
		
		if (! objects.length) {
			objects = [objects];
		}
		
		function map() {
			
			for (var objIndex = 0, objsLength = objects.length; objIndex < objsLength; objIndex++) {
				var obj = new modelClass(objects[objIndex]);
				if (save) {
					obj.save();
				}
				mappedObjects.push(obj);
			}
		}
		
		if (save) {
			Norm.Transaction(map);
		} else {
			map();
		}
		
		return mappedObjects;
	}
	
};
